package com.yifeng.repo.base.security.context.filter;

import com.gomcarter.frameworks.base.json.ErrorCode;
import com.yifeng.repo.base.dto.PermissionDto;
import com.yifeng.repo.base.dto.UserDto;
import com.yifeng.repo.base.params.ConsoleParam;
import com.yifeng.repo.base.security.auth.SecurityCheckerFactory;
import com.yifeng.repo.base.security.auth.constant.SecurityTypeEnum;
import com.yifeng.repo.base.security.auth.exception.SecurityCheckException;
import com.yifeng.repo.base.security.context.manager.RequestContextManager;
import com.yifeng.repo.base.security.context.manager.RequestHeaderManager;
import com.yifeng.repo.base.security.context.params.SecurityCheckParam;
import com.yifeng.repo.base.utils.action.BizCacheAction;
import com.yifeng.repo.base.utils.servlet.AbstractFilter;
import com.yifeng.repo.base.utils.servlet.ResponseHelper;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

import static com.yifeng.repo.base.security.context.manager.RequestHeaderManager.REQ_URI;

/**
 * Created by daibing on 2023/8/22.
 */
@Slf4j
public class AppSecurityFilter extends AbstractFilter {
    private final SecurityTypeEnum defaultSecurityType;
    private final List<SecurityCheckParam> securityCheckParams;

    /**
     * 兼容历史版本，默认是2B员工鉴权
     */
    public AppSecurityFilter(List<String> excludeUris, boolean allowPassByNonToken,
                             boolean checkApi, boolean loadPermission, boolean successIfCheckSecuredException,
                             ConsoleParam accountConsoleParam, BizCacheAction bizCacheAction) {
        super(excludeUris.toArray(new String[0]));
        this.defaultSecurityType = SecurityTypeEnum.BUSINESS;
        this.securityCheckParams = Collections.singletonList(new SecurityCheckParam(
                SecurityTypeEnum.BUSINESS, true, false, false, allowPassByNonToken,
                checkApi, loadPermission, successIfCheckSecuredException));
        Map<SecurityTypeEnum, ConsoleParam> securityType2ConsoleParam = Collections.singletonMap(SecurityTypeEnum.BUSINESS, accountConsoleParam);
        SecurityCheckerFactory.init(this.securityCheckParams, securityType2ConsoleParam, bizCacheAction);
    }

    /**
     * 支持同时指定多种模式鉴权2B2C2S
     */
    public AppSecurityFilter(List<String> excludeUris, List<SecurityCheckParam> securityCheckParams,
                             ConsoleParam accountConsoleParam, ConsoleParam customerConsoleParam, ConsoleParam supplyConsoleParam,
                             BizCacheAction bizCacheAction) {
        super(excludeUris.toArray(new String[0]));
        this.defaultSecurityType = getDefaultSecurityType(securityCheckParams);
        this.securityCheckParams = securityCheckParams;
        Map<SecurityTypeEnum, ConsoleParam> securityType2ConsoleParam = buildConsoleParamMapping(accountConsoleParam, customerConsoleParam, supplyConsoleParam);
        SecurityCheckerFactory.init(this.securityCheckParams, securityType2ConsoleParam, bizCacheAction);
    }

    private SecurityTypeEnum getDefaultSecurityType(List<SecurityCheckParam> securityCheckParams) {
        SecurityCheckParam securityCheckParam = securityCheckParams.stream().filter(SecurityCheckParam::isDefaultSecurity).findFirst().orElse(null);
        if (securityCheckParam == null) {
            return SecurityTypeEnum.BUSINESS;
        }
        return securityCheckParam.getSecurityType();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
        try {
            this.doSecurityFilter(request, response, chain);
        } catch (SecurityCheckException e) {
            log.warn("鉴权检查异常: uri={}, error=", RequestHeaderManager.get(REQ_URI), e);
            ResponseHelper.doError((HttpServletResponse) response, e.getCode(), e.getMessage());
        } catch (Throwable t) {
            log.warn("鉴权内部异常: uri={}, error=", RequestHeaderManager.get(REQ_URI), t);
            ResponseHelper.doError((HttpServletResponse) response, ErrorCode.securityInternalError.getCode(), t.getMessage());
        }
    }
    
    /**
     * 进入filter就必须带token或者用户信息，否则就排除。加入对老业务的兼容（允许无token通行）。
     */
    protected void doSecurityFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 1. 检查是否排除uri
        String uri = ((HttpServletRequest) request).getRequestURI();
        if (this.isExcludeUri(uri.toLowerCase())) {
            chain.doFilter(request, response);
            return;
        }

        // 2. 检查 token：有token就解析token获取用户信息，没有支持直接从请求头获取用户信息
        SecurityTypeEnum securityType = Optional.ofNullable(RequestContextManager.getSecurityType()).orElse(defaultSecurityType);
        String token = SecurityCheckerFactory.get(securityType).checkAndGetToken(request);
        RequestContextManager.setToken(token);

        // 3. 获取鉴权检查参数：如果未配置就直接返回不做检查
        SecurityCheckParam securityCheckParam = securityCheckParams.stream()
                .filter(s -> s.getSecurityType() == securityType)
                .findFirst()
                .orElse(null);
        if (securityCheckParam == null) {
            chain.doFilter(request, response);
            return;
        }

        // 4. 检查用户信息：兼容老业务引入配置允许无token通行
        UserDto userDto = SecurityCheckerFactory.get(securityType).checkAndGetUser(request, token);
        if (userDto == null && securityCheckParam.isAllowPassByNonToken()) {
            log.info("兼容老业务允许无token通行：{}", uri);
            chain.doFilter(request, response);
            return;
        }
        assertNotNull(userDto, "检查接口用戶不能为空！");
        RequestContextManager.setUser(userDto);

        // 5. 检查接口
        if (securityCheckParam.isCheckApi()) {
            assertIsTrue(SecurityCheckerFactory.get(securityType).checkApiSecured(userDto, uri),
                    String.format("检查接口匹配失败：user=%s, uri=%s", userDto.getCode(), uri));
        }

        // 6. 检查权限
        if (!securityCheckParam.isLoadPermission()) {
            chain.doFilter(request, response);
            return;
        }
        PermissionDto permissionDto = SecurityCheckerFactory.get(securityType).checkAndGetPermission(userDto);
        RequestContextManager.setPermission(permissionDto);
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // tomcat 8.x 不支持接口默认实现
        // super.init(filterConfig);
    }

    @Override
    public void destroy() {
        // tomcat 8.x 不支持接口默认实现
        // super.destroy();
    }

    private Map<SecurityTypeEnum, ConsoleParam> buildConsoleParamMapping(ConsoleParam accountConsoleParam,
                                                                         ConsoleParam customerConsoleParam,
                                                                         ConsoleParam supplyConsoleParam) {
        Map<SecurityTypeEnum, ConsoleParam> securityType2ConsoleParam = new HashMap<>(3);
        if (accountConsoleParam != null) {
            securityType2ConsoleParam.put(SecurityTypeEnum.BUSINESS, accountConsoleParam);
        }
        if (customerConsoleParam != null) {
            securityType2ConsoleParam.put(SecurityTypeEnum.CUSTOMER, customerConsoleParam);
        }
        if (supplyConsoleParam != null) {
            securityType2ConsoleParam.put(SecurityTypeEnum.SUPPLY, supplyConsoleParam);
        }
        return securityType2ConsoleParam;
    }

    private void assertNotNull(Object object, String message) {
        if (object == null) {
            throw new SecurityCheckException(message);
        }
    }

    private void assertIsTrue(boolean expression, String message) {
        if (!expression) {
            throw new SecurityCheckException(message);
        }
    }
}
